﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.SqlClient;
using System.Security.Cryptography;
using System.IO;
using System.Data;
using System.Text;

namespace SqlShareAPI
{
    public class SSAuthentication
    {
        private SSSqlConnection _conn;
        public bool IsAuthenticated { get; private set; }
        private string _username;
        private UserSession _session;

        internal SSAuthentication(string username, SSSqlConnection conn)
        {
            _conn = conn;
            _username = username;
            IsAuthenticated = false;
            _session = null;    // set on authentication
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="username"></param>
        /// <param name="token"></param>
        /// <param name="conn">This connection should be to the web partion of the database</param>
        /// <returns></returns>
        public bool AuthenticateToken(string token)
        {
            // check stored sessions for username / auth token
            SqlCommand cmd = new SqlCommand("sp_auth_token", _conn.MetaConnection);
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.Add("@username", SqlDbType.VarChar).Value = _username;
            cmd.Parameters.Add("@token", SqlDbType.VarChar).Value = token;

            int found = int.Parse(cmd.ExecuteScalar().ToString());
            IsAuthenticated = found != 0;

            if (IsAuthenticated)
            {
                CreateSession(token);
            }

            return IsAuthenticated;
        }        

        /// <summary>
        /// 
        /// </summary>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <param name="conn"></param>
        /// <returns></returns>
        public bool AuthenticatePassword(string password)
        {
            
                // first attempt to check to see if we are a valid user
                string passEnc = DESEncrypt(_username + password);
                SqlCommand cmd = new SqlCommand("sp_auth_password", _conn.MetaConnection);
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Parameters.Add("@username", SqlDbType.VarChar).Value = _username;
                cmd.Parameters.Add("@pass", SqlDbType.VarChar).Value = passEnc;

                int found = 0;
                int.TryParse(cmd.ExecuteScalar().ToString(), out found);

                IsAuthenticated = found != 0;
                if (IsAuthenticated)
                {
                    // create a new token and save cookie
                    // token: username + timestamp encrytped 
                    string token = DESEncrypt(_username + DateTime.Now.Millisecond);

                    // if successful create a token
                    cmd = new SqlCommand("sp_session_create", _conn.MetaConnection);
                    cmd.CommandType = CommandType.StoredProcedure;

                    cmd.Parameters.Add("@username", SqlDbType.VarChar).Value = _username;
                    cmd.Parameters.Add("@token", SqlDbType.VarChar).Value = token;

                    cmd.ExecuteNonQuery();

                    CreateSession(token);
                }
                return IsAuthenticated;         
        }

        /// <summary>
        /// Query for proper user information to create an UserSession object
        /// </summary>
        /// <param name="token"></param>
        private void CreateSession(string token)
        {
            SqlCommand cmd = new SqlCommand("sp_user_get", _conn.MetaConnection);
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.Add("@username", SqlDbType.VarChar).Value = _username;

            SqlDataReader reader = cmd.ExecuteReader();
            if (!reader.Read())
            {
                throw new Exception("SQL Error: Was unable to obtain user information");
            }

            _session = new UserSession(_username, 
                                        token, 
                                        reader["fullname"].ToString(), 
                                        bool.Parse(reader["is_admin"].ToString()), 
                                        this,
                                        _conn);
            reader.Close();
        }

        /// <summary>
        /// user must authenticate either by token or password before retrieving a session otherwise an exception is thrown
        /// </summary>
        /// <returns></returns>
        public UserSession GetUserSession()
        {
            if (!IsAuthenticated)
            {
                throw new Exception("username: " + _username + " was not authenticated. Could not create UserSession");
            }
            return _session;
        }

        /// <summary>
        /// Create a new user and password
        /// </summary>
        /// <param name="newUserName"></param>
        /// <param name="password"></param>
        /// <param name="dbCollection"></param>
        /// <returns></returns>
        public bool AdminCreateUser(string newUserName, string password, string fullname, bool asAdmin)
        {
            if (!_session.IsAdmin)
            {
                throw new Exception("Unable to create new user. Current user is not an admin.");
            }

            SqlTransaction contentTrans = _conn.AdminContentConnection.BeginTransaction();
            SqlCommand cmd;

            // create user account on content database
            cmd = new SqlCommand("sp_metauser_create", _conn.AdminContentConnection, contentTrans);
            cmd.CommandType = System.Data.CommandType.StoredProcedure;

            cmd.Parameters.Add("@username", SqlDbType.VarChar).Value = newUserName;
            cmd.Parameters.Add("@password", SqlDbType.VarChar).Value = SSSqlConnection.GlobalContentPassword;

            try
            {
                cmd.ExecuteNonQuery();
            }
            catch (Exception e)
            {
                contentTrans.Rollback();
                Console.WriteLine(e.Message);
                return false;
            }


            // create user entry in meta database
            SqlTransaction metaTrans = _conn.MetaConnection.BeginTransaction(); 
            string passEnc = DESEncrypt(newUserName + password);

            cmd = new SqlCommand("sp_user_create", _conn.MetaConnection, metaTrans);
            cmd.CommandType = System.Data.CommandType.StoredProcedure;

            cmd.Parameters.Add("@username", SqlDbType.VarChar).Value = newUserName;
            cmd.Parameters.Add("@password", SqlDbType.VarChar).Value = passEnc;
            cmd.Parameters.Add("@fullname", SqlDbType.VarChar).Value = fullname;
            cmd.Parameters.Add("@is_admin", SqlDbType.Bit).Value = asAdmin;            

            try
            {
                cmd.ExecuteNonQuery();

                // commit both transactions
                contentTrans.Commit();
                metaTrans.Commit();
            }
            catch (Exception e)
            {
                contentTrans.Rollback();
                metaTrans.Rollback();
                Console.WriteLine(e.Message);
                return false;
            }

            return true;
        }

        /// Cryptography code        
        const string DESKey = "AQWSEDRF";
        const string DESIV = "HGFEDCBA";


        private static string DESDecrypt(string stringToDecrypt)//Decrypt the content
        {
            byte[] key;
            byte[] IV;

            byte[] inputByteArray;
            try
            {

                key = Convert2ByteArray(DESKey);

                IV = Convert2ByteArray(DESIV);

                int len = stringToDecrypt.Length; inputByteArray = Convert.FromBase64String(stringToDecrypt);


                DESCryptoServiceProvider des = new DESCryptoServiceProvider();

                MemoryStream ms = new MemoryStream(); CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(key, IV), CryptoStreamMode.Write);
                cs.Write(inputByteArray, 0, inputByteArray.Length);

                cs.FlushFinalBlock();

                Encoding encoding = Encoding.UTF8; return encoding.GetString(ms.ToArray());
            }

            catch (System.Exception ex)
            {

                throw ex;
            }

        }

        public static string DESEncrypt(string stringToEncrypt)// Encrypt the content
        {
            byte[] key;
            byte[] IV;

            byte[] inputByteArray;
            try
            {

                key = Convert2ByteArray(DESKey);

                IV = Convert2ByteArray(DESIV);

                inputByteArray = Encoding.UTF8.GetBytes(stringToEncrypt);
                DESCryptoServiceProvider des = new DESCryptoServiceProvider();

                MemoryStream ms = new MemoryStream(); CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(key, IV), CryptoStreamMode.Write);
                cs.Write(inputByteArray, 0, inputByteArray.Length);

                cs.FlushFinalBlock();

                return Convert.ToBase64String(ms.ToArray());
            }

            catch (System.Exception ex)
            {

                throw ex;
            }

        }

        private static byte[] Convert2ByteArray(string strInput)
        {

            int intCounter; char[] arrChar;
            arrChar = strInput.ToCharArray();

            byte[] arrByte = new byte[arrChar.Length];

            for (intCounter = 0; intCounter <= arrByte.Length - 1; intCounter++)
                arrByte[intCounter] = Convert.ToByte(arrChar[intCounter]);

            return arrByte;
        }
    }


    public class LoginState
    {
        private const string UNKNOWN = "UNKNOWN";
        public static LoginState UNKNOWN_USER = new LoginState(UNKNOWN);
        private string _userName;

        public LoginState(string username)
        {
            _userName = username;
        }

        public string UserName { get { return _userName; } }
        public bool IsLoggedIn { get { return !_userName.Equals(UNKNOWN); } }

    }
}
